#ifndef LINKED_LIST_POOL_H
#define LINKED_LIST_POOL_H

#include <vector>
#include <cstdint>
#include <iterator>

/**
 * @brief Pool of singly linked lists.
 *
 *  Should not be used as general purpose container. Use only for algorithms that require linked lists ability
 * to split and concatenate them. All the data is owned by LinkedListPool.
 *
 * In contrast to std::list and std::forward_list doesn't allocate each node separately. LinkedListPool can reserve
 * all the memory for multiple lists during construction. Uses std::vector as backing container.
 */
template<class T>
class LinkedListPool
{
    using IndexType = size_t;
    struct Item {
        IndexType next;
        T value;
    };
public:
    /**
     * @brief Single list within LinkedListPool.
     *
     * List only refers to chain of elements. Copying it doesn't copy any element. Item data is owned by
     * LinkedListPool.
     *
     * Use LinkedListPool::makeList to create non-empty list.
     */
    class List
    {
        IndexType head = 0;
        IndexType tail = 0;
        friend class LinkedListPool;
        List(IndexType head, IndexType tail)
            : head(head)
            , tail(tail)
        {}
    public:
        /**
         * @brief Create an empty list
         */
        List() = default;
    };

    /**
     * @brief List iterator.
     *
     * Iterators don't get invalidated by adding items to list, but the items may be relocated.
     */
    class ListIterator
    {
        IndexType index = 0;
        LinkedListPool<T> *pool = nullptr;
        ListIterator(IndexType index, LinkedListPool<T> *pool)
            : index(index)
            , pool(pool)
        {}

        friend class LinkedListPool<T>;
    public:
        using iterator_category = std::forward_iterator_tag;
        using value_type = T;
        using difference_type = size_t;
        using pointer = T*;
        using reference = T&;
        ListIterator() = default;
        reference operator*()
        {
            return pool->data[index].value;
        }
        ListIterator &operator++()
        {
            index = pool->data[index].next;
            return *this;
        }
        ListIterator operator++(int)
        {
            ListIterator tmp(*this);
            operator++();
            return tmp;
        }
        bool operator!=(const ListIterator &b) const
        {
            return index != b.index || pool != b.pool;
        };
        /**
         * @brief Test if iterator points to valid value.
         */
        operator bool() const
        {
            return index;
        }
    };

    /**
     * @brief Create a linked list pool with capacity for \a initialCapacity list items.
     * @param initialCapacity number of elements to preallocate.
     */
    LinkedListPool(size_t initialCapacity)
        : data(1)
    {
        data.reserve(initialCapacity + 1); // [0] element reserved
    }

    /**
     * @brief Create a list containing single item.
     *
     * Does not invalidate any iterators, but may cause item relocation when initialCapacity is exceeded.
     * @param value value of element that will be inserted in the created list
     * @return List containing single value \a value .
     */
    List makeList(const T &value)
    {
        size_t position = data.size();
        data.push_back(Item{0, value});
        return {position, position};
    }

    /**
     * @brief Split list and return second half.
     *
     * After performing the operation, list passed as argument and return list point to the same items. Modifying them
     * will affect both lists.
     *
     * @param list The list that needs to be split.
     * @param head Iterator to the first item in new list. Needs to be within \a list .
     * @return Returns suffix of \a list.
     */
    List splitTail(const List &list, const ListIterator &head)
    {
        return List {head.index, list.tail};
    }

    /**
     * @brief Create list iterator from list.
     * @param list
     * @return Iterator pointing to the first item in the list.
     */
    ListIterator head(const List &list)
    {
        return iteratorFromIndex(list.head);
    }

    ListIterator end(const List &list)
    {
        return std::next(iteratorFromIndex(list.tail));
    }

    List append(const List &head, const List &tail)
    {
        List result{head.head, tail.tail};
        data[head.tail].next = tail.head;
        return result;
    }
private:
    ListIterator iteratorFromIndex(IndexType index)
    {
        return ListIterator{ index, this };
    }

    std::vector<Item> data;
};


#endif // LINKED_LIST_POOL
